#pragma once

#include "../common/Common.h"
#include "../common/Message.h"
#include "Request.h"

namespace rpc
{
    namespace client
    {

        class Provider
        {
        public:
            using ptr = std::shared_ptr<Provider>;
            Provider(const Request::ptr &req) : _request(req) {}

            bool registryMethod(const BaseConnection::ptr &conn, const string &method, const Address &addr)
            {
                auto req = MessageFactory::create<ServiceRequest>();

                req->setRid(uuid());
                req->setHost(addr);
                req->setMethod(method);
                req->setMtype(MType::REQ_SERVICE);
                req->setOpType(ServiceOpType::SERVICE_REGISTRY);

                BaseMessage::ptr resp;
                if (!_request->send(conn, req, resp))
                {
                    ELOG("服务 %s 注册请求构造失败", method.c_str());
                    return false;
                }

                auto serviceResp = std::dynamic_pointer_cast<ServiceResponse>(resp);
                if (!serviceResp)
                {
                    ELOG("响应类型向下转型失败");
                    return false;
                }

                if (serviceResp->rcode() != RCode::RCODE_OK)
                {
                    ELOG("服务注册请求失败 %s ", errReason(serviceResp->rcode()).c_str());
                    return false;
                }

                return true;
            }

        private:
            Request::ptr _request;
        };

        class MethodHost
        {
        public:
            using ptr = std::shared_ptr<MethodHost>;

            MethodHost() : _idx(0) {}
            MethodHost(const vector<Address> &hosts)
                : _hosts(hosts.begin(), hosts.end()), _idx(0) {}

            // 中途收到了服务上线请求后被调用
            void appendHost(const Address &addr)
            {
                unique_lock<mutex> lock(_mtx);
                _hosts.emplace_back(addr);
            }

            void removeHost(const Address &addr)
            {
                // 中途收到了服务下线请求后被调用
                unique_lock<mutex> lock(_mtx);
                for (auto it = _hosts.begin(); it != _hosts.end(); it++)
                {
                    if (*it == addr)
                    {
                        _hosts.erase(it);
                        break;
                    }
                }
            }

            Address chooseHost()
            {
                unique_lock<mutex> lock(_mtx);
                return _hosts[_idx++ % _hosts.size()];
            }

            bool empty()
            {
                unique_lock<mutex> lock(_mtx);
                return _hosts.empty();
            }

        private:
            mutex _mtx;
            size_t _idx;
            vector<Address> _hosts;
        };

        class Discover
        {
        public:
            using OfflineCallback = std::function<void(const Address &)>;
            using ptr = std::shared_ptr<Discover>;

            Discover(const Request::ptr &req, const OfflineCallback &cb) : _request(req), _cb(cb) {}

            bool serviceDiscovery(const BaseConnection::ptr &conn, const string &method, Address &host)
            {
                // 当前所保管的提供者信息存在，则直接返回地址
                {
                    unique_lock<mutex> lock(_mtx);
                    auto it = _methodHosts.find(method);
                    if (it != _methodHosts.end())
                    {
                        if (!it->second->empty())
                        {
                            host = it->second->chooseHost();
                            return true;
                        }
                    }
                }

                // 当前的服务提供者为空
                auto req = MessageFactory::create<ServiceRequest>();
                req->setRid(uuid());
                req->setMethod(method);
                req->setMtype(MType::REQ_SERVICE);
                req->setOpType(ServiceOpType::SERVICE_DISCOVER);

                BaseMessage::ptr resp;
                if (!_request->send(conn, req, resp))
                {
                    ELOG("服务发现失败");
                    return false;
                }

                auto serviceResp = std::dynamic_pointer_cast<ServiceResponse>(resp);
                if (!serviceResp)
                {
                    ELOG("服务发现失败，服务响应向下转型失败！");
                    return false;
                }

                if (serviceResp->rcode() != RCode::RCODE_OK)
                {
                    ELOG("服务发现失败 %s ", errReason(serviceResp->rcode()).c_str());
                    return false;
                }

                // 能走到这里，代表当前是没有对应的服务提供主机的
                unique_lock<mutex> lock(_mtx);
                auto methodHost = std::make_shared<MethodHost>(serviceResp->host());
                if (methodHost->empty())
                {
                    ELOG("没有能提供 %s 服务的主机", method);
                    return false;
                }
                _methodHosts.emplace(method, methodHost);

                host = methodHost->chooseHost();

                return true;
            }

            // 这个接口是提供给Dispatcher模块进行服务上线下线请求处理的回调函数
            void onServiceRequest(const BaseConnection::ptr &conn, const ServiceRequest::ptr &req)
            {
                // 1. 判断是上线还是下线请求，如果都不是那就不用处理了
                auto optype = req->opType();
                auto method = req->method();
                unique_lock<mutex> lock(_mtx);
                if (optype == ServiceOpType::SERVICE_ONLINE)
                {
                    // 2. 上线请求：找到MethodHost，向其中新增一个主机地址
                    auto it = _methodHosts.find(method);
                    if (it == _methodHosts.end())
                    {
                        auto methodHost = std::make_shared<MethodHost>();
                        methodHost->appendHost(req->host());
                        _methodHosts.emplace(method, methodHost);
                    }
                    else
                    {
                        it->second->appendHost(req->host());
                    }
                }
                else if (optype == ServiceOpType::SERVICE_OFFLINE)
                {
                    // 3. 下线请求：找到MethodHost，从其中删除一个主机地址
                    auto it = _methodHosts.find(method);
                    if (it == _methodHosts.end())
                    {
                        return;
                    }
                    it->second->removeHost(req->host());
                    _cb(req->host());
                }
            }

        private:
            mutex _mtx;
            unordered_map<string, MethodHost::ptr> _methodHosts;
            Request::ptr _request;
            OfflineCallback _cb;
        };

    };
};